home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Ian & Stuart's Australian Mac 1993 September
/
September 93.iso
/
Archives
/
Sound
/
MIDI
/
MIDI Utilities
/
CMU Midi Toolkit
/
Source
/
macmidi.c
< prev
next >
Wrap
Text File
|
1988-01-11
|
28KB
|
905 lines
/*
* macmidi.c -- MIDI interface for the Macintosh
*
* this module handles midi initialization, termination, timing, and i/o.
* because it was originally written for the Roland MPU-401
* IBM PC interface, some procedures in this module still have
* the letters"mpu" in them -- history does leave its mark!
*/
/*****************************************************************************
* Change Log
* Date | Change
*-----------+-----------------------------------------------------------------
* 31-Dec-85 | Created changelog
* 30-Sep-86 | JMaloney: Converted "mpu.c" to Macintosh
* | and renamed to "macmidi.c"
* 7-Oct-86 | JMaloney: Put in abort watcher
* 9-Oct-86 | JMaloney: Allow multiple midi output ports
* 21-Oct-86 | JMaloney: Do MIDI out via Mac serial drivers
* 22-Oct-86 | JMaloney: Moved abort handling to userio.c
* 8-Jan-87 | JMaloney: Converted to Lightspeed C
* 17-Jan-87 | JMaloney: Added synth_init
*****************************************************************************/
/* NOTE: the interface between this module and the midi input driver:
*
* the midi input driver parses incoming midi data in order to do
* things like filtering out system exlusive messages.
* to avoid reparsing at this level, the data is buffered in a
* queue of messages rather than a simple byte stream. each message
* is a 4-byte block. the first byte is a status byte and the following
* bytes (one or two) are data bytes. the fourth byte is unused.
*
* system exclusive messages are handled by copying the midi exclusive
* data into a separate buffer provided by the application program
* through the midi_buffer() call. the input driver takes care of this.
*/
#include "switches.h"
#ifdef LIGHTSPEED
#include "Proto.h"
#include <StdIO.h>
#endif
#ifdef MPW
#include <StdIO.h>
#endif
#include "cext.h"
#include "cmdline.h"
#include "midibuff.h"
#include "midicode.h"
#include "mididriver.h"
#include "mpu.h"
#include "pitch.h"
#include "userio.h"
/****************************************************************************
*
* constants
*
****************************************************************************/
/* define DEBUG to enable extra debugging */
#define DEBUG
/* the modem port, also called port A */
#define portA 0
/* the printer port, also called port B */
#define portB 1
/* port numbers are in the range 0..MAX_PORTS-1 */
#define CHANNELS_PER_PORT 16
#define MAX_PORTS ((MAX_CHANNELS + CHANNELS_PER_PORT - 1) / CHANNELS_PER_PORT)
/****************************************************************************
*
* timer related stuff
*
****************************************************************************/
/* a tick is 1/60 of a second
*
* the following tables and routines are used to convert
* between ticks and hundredths of a second
* the maximum error going from ticks to hundredths is 2/500ths of a second
* the maximum error going from hundredths to ticks is 1/300th of a second
*/
/* conversion tables */
private ulong toHundredths[] = {0, 2, 3};
private ulong toTicks[] = {0, 1, 1, 2, 2};
#define TICKS_TO_HUNDREDTHS(t) ((((t) / 3L) * 5L) + toHundredths[(t) % 3L])
#define HUNDREDTHS_TO_TICKS(t) ((((t) / 5L) * 3L) + toTicks[(t) % 5L])
/****************************************************************************
*
* variables shared with other modules
*
****************************************************************************/
boolean do_midi_thru = false;
/* exported: copy midi in to midi out */
boolean miditrace = false;
/* exported: enables printed trace of MIDI output */
boolean musictrace = false;
/* exported: enables printed trace of commands */
boolean ctrlFilter = true;
/* exported: suppress continuous controller data */
int num_channels = 16; /* exported: should be controlled by the user,
* depending on the number of midi output devices
* (you get 16 channels per device), but I haven't
* decided out how to do it yet!
*/
/****************************************************************************
*
* midi input and system exclusive buffers
* NOTE: these buffers are SHARED with the midi input driver
*
****************************************************************************/
/* midi input buffer */
byte buff[BUFF_SIZE]; /* data buffer */
int buffhead = 0; /* buffer head and tail pointers */
int bufftail = 0;
/* user supplied system exclusive buffer */
byte *xbuff = NULL; /* address of the user-supplied buffer */
int xbuffmask; /* mask for circular buffer address calculation */
int xbuffhead = 0; /* buffer head and tail pointers */
int xbufftail = 0;
/****************************************************************************
*
* variables private to this module
*
****************************************************************************/
private boolean initialized = false;
/* set by musicinit, cleared by musicterm
* indicates the current state of the midi drivers */
private boolean simulate = false;
/* used for debugging
* if simulate is true, no midi output is actually performed */
private boolean user_retuning_on = false;
/* true if the user defined his own scale
* set by read_tuning, never cleared */
private short bend[MAX_CHANNELS];
/* current pitch bend on each channel
* used to avoid sending extra pitch bend commands
* NOTE: voice 1's bend is in bend[0] */
private short midi_prog[MAX_CHANNELS];
/* current program ("preset" or "instrument") for each channel
* used to avoid sending extra program change commands
* NOTE: voice 1's program is in program[0] */
private pitch_table pitch_tab[128];
/* user's scale definition
* initialize by read_tuning if the user specifies "-tune" option
* NOTE: not initialized or used otherwise */
private ulong ticksAtStart = 0;
/* clock ticks at time of last musicinit or timereset
* ASSUME: tick clock never wraps. this is a good assumption, since
* the tick clock is set to zero when the power is turned on and the
* tick counter is 32 bits. the Macintosh would need to be on for
* 828.5 days for the tick counter to wrap around! */
/****************************************************************************
*
* routines private to this module
*
****************************************************************************/
void fixup(void);
void map_channel(int, int *, int *);
void putbyte(int, byte);
/****************************************************************************
* exclusive
* Inputs:
* boolean onflag: set to true to receive midi exclusive data
* Effect:
* tells midi i/o device to accept exclusive messages into buffer
* (this is a noop on the Macintosh; it is kept for compatability)
****************************************************************************/
public void exclusive(onflag)
boolean onflag;
{
if (!initialized) fixup();
if (musictrace) gprintf(TRANS, "exclusive: %s\n", (onflag ? "on" : "off"));
}
/****************************************************************************
* fixup
* Effect:
* print error message and call musicinit
****************************************************************************/
private void fixup()
{
gprintf(ERROR, "You forgot to call musicinit. I'll do it for you.\n");
musicinit();
}
/****************************************************************************
* getbuf
* Inputs:
* boolean waitflag: true if routine should wait for data
* byte * p: pointer to 4 byte data destination
* Returns:
* boolean: true if data was written to *p false otherwise
* Effect:
* copies data from buffer to *p
* will wait for buffer to become nonempty if waitflag is true
****************************************************************************/
public boolean getbuf(waitflag, p)
boolean waitflag;
byte *p;
{
if (!initialized) return false;
if (waitflag) {
while (buffhead == bufftail) /* wait */;
} else {
if (buffhead == bufftail) return false;
}
*(ulong *) p = *(ulong *) (buff + buffhead);
buffhead = (buffhead + 4) & BUFF_MASK;
/* the previous two lines are an optimization of:
*
* *p++ = buff[buffhead++];
* *p++ = buff[buffhead++];
* *p++ = buff[buffhead++];
* buffhead++;
*
* if (buffhead >= BUFF_SIZE) buffhead = 0;
*/
return true;
}
/****************************************************************************
* getkey
* Inputs:
* boolean waitflag: if true wait until key depression else return immediately
* Returns:
* int: key number of the key which has been depressed, or
* -1 if waitflag is false and no key has been depressed
* int * velocity: set to the velocity of the key depressed, if there is one
* Effect:
* reads midi input buffer until it finds a key up or down event
* throws away all buffered midi events up until the key event
* if waitflag is true this routine will block until a key is depressed
****************************************************************************/
public int getkey(waitflag, velocity)
boolean waitflag;
int *velocity;
{
byte msg[4];
int key;
if (!initialized) return -1;
*velocity = 0;
while (true) { /* process data until you find a note */
/* look for data and exit if none found */
/* NOTE: waitflag will force getbuf to wait until data arrives */
if (!getbuf(waitflag, msg)) { /* nothing there */
key = -1;
break;
} else if ((msg[0] & MIDI_CODE_MASK) == MIDI_ON_NOTE) {
if (msg[2] == 0) { /* velocity 0 implies note off */
*velocity = 0;
key = (msg[1] - 12) + 128; /* add 128 to show note off */
} else {
*velocity = msg[2]; /* note on */
key = (msg[1] - 12);
}
break;
} else if ((msg[0] & MIDI_CODE_MASK) == MIDI_OFF_NOTE) {
*velocity = 0;
key = (msg[1] - 12) + 128; /* add 128 to show note off */
break;
}
}
if (musictrace) {
if (key != -1)
gprintf(TRANS,
"getkey got %d (velocity: %d)\n", key, *velocity);
}
return key;
}
/****************************************************************************
* gettime
* Returns:
* ulong: the time in 100ths of seconds since the last timereset
****************************************************************************/
public ulong gettime()
{
register ulong ticks = TickCount() - ticksAtStart;
if (initialized) abort_check(); /* give user a chance to abort */
return TICKS_TO_HUNDREDTHS(ticks);
}
/****************************************************************************
* l_rest
* Inputs:
* ulong dur: amount of time to rest, in 100ths of a second
* Effect:
* waits until the amount of time specified has lapsed
****************************************************************************/
public void l_rest(dur)
ulong dur;
{
l_restuntil(gettime() + dur);
}
/****************************************************************************
* l_restuntil
* Inputs:
* ulong absTime: absolute time to rest until, in 100ths of a second
* Effect:
* waits until the specified time has been reached (absolute time
* is in 100ths of seconds since the last timereset call)
****************************************************************************/
public void l_restuntil(absTime)
ulong absTime;
{
ulong now = gettime();
ulong junk;
if (absTime > now) Delay(HUNDREDTHS_TO_TICKS(absTime - now), &junk);
/* else time <= now, so return immediately */
}
/****************************************************************************
* map_channel
* Inputs:
* int voice: voice number (or "abstract channel number")
* Returns:
* int * port: set to the midi output port on which to send midi data
* int * channel: set to channel number to be used in the midi command
* Effect:
* maps a voice number onto a (port, channel) pair
* NOTE: do not confuse a "voice" number with a "channel" number
* voice numbers start with 1 whereas channel numbers start with 0.
* Implementation:
* there may be several different hardware ports for midi output. ports
* are numbered 0,1,2, etc. there are sixteen midi channels per
* port, numbered 0 through 15. the global variable "num_channels"
* should be initialized to (16 * <number of ports>). right now, there is no
* way to intialize it except by re-compiling; ideally, it would be
* a command-line option.
****************************************************************************/
private void map_channel(voice, port, channel)
int voice;
int *port;
int *channel;
{
#ifdef DEBUG
if ((voice < 1) || (voice > num_channels)) {
gprintf(ERROR,
"Implementation error (macmidi.c): invalid voice number %d\n",
voice);
*port = 0; /* if error, default to channel 0, port 0 and go on */
*channel = 0;
return;
}
#endif
*port = (voice -1) >> 4; /* divide by 16; port is the high bits */
*channel = (voice - 1) & 0x0F; /* channel is low 4 bits */
}
/****************************************************************************
* metronome
* Inputs:
* int onflag: true or false
* Effect:
* enables (true) or disables (false) the midi hardware metronome function
* must be called before musicinit for MPU-401
* this may be a noop if there is no metronome in the midi hardware
* (as is the case on the Macintosh)
****************************************************************************/
public void metronome(onflag)
int onflag;
{
/* not supported on the Macintosh */
}
/****************************************************************************
* midi_bend
* Inputs:
* int voice: midi voice on which to send data
* int value: pitch bend value
* Effect:
* sends a midi pitch bend message
****************************************************************************/
public void midi_bend(voice, value)
int voice;
int value;
{
int port, midi_chan;
if (!initialized) fixup();
if (bend[voice - 1] == value) return; /* noop if bend already bent */
if (musictrace)
gprintf(TRANS, "midi_bend: ch %d, val %d\n", voice, value);
bend[voice - 1] = value; /* remember current bend */
map_channel(voice, &port, &midi_chan);
putbyte(port, MIDI_BEND | midi_chan);
putbyte(port, MIDI_DATA(value));
putbyte(port, MIDI_DATA(value >> 7));
if (miditrace) gprintf(TRANS, "[%d]\n", port);
}
/****************************************************************************
* midi_buffer
* Inputs:
* byte * buffer: the buffer address
* int size: number of bytes in buffer
* Returns:
* boolean: false if size is less than 16 or buffer is NULL, otherwise true
* Effect:
* tells interrupt routine to store system exclusive messages in buffer.
* the largest power of 2 bytes less than size will be used.
* xbuffhead and xbufftail will be initialized to zero,
* and xbufftail will be one greater than the index of the last
* system exclusive byte read from mpu401.
****************************************************************************/
public boolean midi_buffer(buffer, size)
byte *buffer;
int size;
{
int mask;
mask = 16 - 1;
if ((size < 16) || (buffer == NULL)) return false;
while ((mask < size) && (mask > 0))
mask = (mask << 1) + 1;
xbuff = NULL; /* turn off buffering */
xbuffmask = mask >> 1;
xbuffhead = xbufftail = 0;
xbuff = buffer; /* set buffer; this turns on buffering */
return true;
}
/****************************************************************************
* midi_cont
* Inputs:
* boolean onflag: true allows continuous controller data to pass through
* Effect:
* continuous controller data from the midi controller (e.g. pitch bends)
* are normally filtered out. midi_cont allows one to accept this data.
* NOTE:
* if continuous controller data is accepted one must read and empty the
* input buffer more frequently to avoid data overruns.
****************************************************************************/
public void midi_cont(onflag)
boolean onflag;
{
ctrlFilter = !onflag;
}
/****************************************************************************
* midi_ctrl
* Inputs:
* int voice: midi voice on which to send data
* int control: control number
* int value: control value
* Effect:
* sends a midi control change message
****************************************************************************/
public void midi_ctrl(voice, control, value)
int voice;
int control;
int value;
{
int port, midi_chan;
if (!initialized) fixup();
if (musictrace)
gprintf(TRANS, "midi_ctrl: ch %d, ctrl %d, val %d\n",
voice, control, value);
map_channel(voice, &port, &midi_chan);
putbyte(port, MIDI_CTRL | midi_chan);
putbyte(port, MIDI_DATA(control));
putbyte(port, MIDI_DATA(value));
if (miditrace) gprintf(TRANS, "[%d]\n", port);
}
/****************************************************************************
* midi_exclusive
* Inputs:
* int port: port number to send on
* byte * msg: pointer to a midi exclusive message, terminated by 0xF7
* Effect:
* sends a midi exclusive message
****************************************************************************/
#define INTERBYTE_DELAY 10
public void midi_exclusive(port, msg)
int port;
byte *msg;
{
int i; /* for DX7 delay loop */
int count = 0; /* counter for formatting midi byte trace */
/* if user mistakenly called midi_exclusive instead of exclusive,
* the argument will be true or false, both of which are highly
* unlikely valid arguments for midi_exclusive:
*/
if ((msg == (byte *) false) || (msg == (byte *) true)) {
gprintf(FATAL, "midi_exclusive: invalid argument %d.", (int) msg);
clean_exit();
}
if (!initialized) fixup();
if (musictrace) gprintf(TRANS, "midi_exclusive\n");
if (miditrace) gprintf(TRANS, "\n");
while (*msg != MIDI_EOX) {
putbyte(port, *msg);
msg++;
count++;
if (miditrace && ((count % 16) == 0)) {
gprintf(TRANS, "[%d]\n", port);
}
/* this is a delay loop, without which your DX7 will crash */
for (i = INTERBYTE_DELAY; i > 0; i--)
abort_check();
}
putbyte(port, MIDI_EOX);
if (miditrace) gprintf(TRANS, "[%d]\n", port);
}
/****************************************************************************
* midi_note
* Inputs:
* int voice: midi voice on which to send data
* int pitch: midi pitch code
* int velocity: velocity with which to sound it (0=> release)
* Effect:
* sends a midi note-on message
****************************************************************************/
public void midi_note(voice, pitch, velocity)
int voice;
int pitch;
int velocity;
{
int port, midi_chan;
if (!initialized) fixup();
if (musictrace)
gprintf(TRANS, "midi_note: ch %d, key %d, vel %d\n",
voice, pitch, velocity);
if (user_retuning_on) {
/* set correct pitch bend if necessary */
if (velocity != 0) midi_bend(voice, pitch_tab[pitch+12].pbend);
pitch = pitch_tab[pitch+12].ppitch;
}
map_channel(voice, &port, &midi_chan);
putbyte(port, MIDI_ON_NOTE | midi_chan);
putbyte(port, MIDI_DATA(12 + pitch)); /* cmu to midi standard pitch */
putbyte(port, MIDI_DATA(velocity));
if (miditrace) gprintf(TRANS, "[%d]\n", port);
}
/****************************************************************************
* midi_program
* Inputs:
* int voice: midi voice on which to send midi program change message
* int prog: program number to send (decremented by 1 before
* being sent as midi data)
* Effect:
* sends a program change message
****************************************************************************/
public void midi_program(voice, prog)
int voice;
int prog;
{
int port, midi_chan;
if (!initialized) fixup();
if (prog == midi_prog[voice - 1]) return; /* noop if program already set */
if (musictrace)
gprintf(TRANS, "midi_program: ch %d, prog %d\n", voice, prog);
midi_prog[voice - 1] = prog; /* remember new setting */
map_channel(voice, &port, &midi_chan);
putbyte(port, MIDI_CH_PROGRAM | midi_chan);
putbyte(port, MIDI_PROGRAM(prog));
if (miditrace) gprintf(TRANS, "[%d]\n", port);
}
/****************************************************************************
* midi_thru
* Inputs:
* boolean onflag: if true, enable midi thru mode
* Effect:
* in midi-thru mode, the midi input stream is copied to midi out
* (the default is enabled; disable with cmdline -block)
****************************************************************************/
public void midi_thru(onflag)
boolean onflag;
{
do_midi_thru = onflag;
/* NOTE: midi thru is not supported on the Macintosh */
}
/****************************************************************************
* midi_touch
* Inputs:
* int voice: midi voice on which to send data
* int value: control value
* Effect:
* sends a midi after touch message
****************************************************************************/
public void midi_touch(voice, value)
int voice;
int value;
{
int port, midi_chan;
if (!initialized) fixup();
if (musictrace)
gprintf(TRANS, "midi_touch: ch %d, val %d\n", voice, value);
map_channel(voice, &port, &midi_chan);
putbyte(port, MIDI_TOUCH | midi_chan);
putbyte(port, MIDI_DATA(value));
if (miditrace) gprintf(TRANS, "[%d]\n", port);
}
/****************************************************************************
* mpuexists
* Inputs:
* boolean flag: false to simulate midi hardware
* Effect:
* if argument is false, indicates no midi hardware is on the machine
* (this feature is useful for debugging)
****************************************************************************/
public void mpuexists(flag)
boolean flag;
{
simulate = !flag;
}
/****************************************************************************
* musicinit
* Effect:
* check for miditrace and musictrace switches
* read tuning file if necessary (this is done at most once)
* initialize the midi device drivers
* initialize bend and program arrays (first time)
* reset the clock
****************************************************************************/
public void musicinit()
{
static boolean first_time = true;
/* set to false by first call to musicinit
* used to avoid command-line processing, re-tuning more than once */
int i;
#ifndef APPLICATION
if (first_time) { /* do this code only once */
/* process command-line switches, including "-tune" */
char *tuneFile = cl_option("-tune");
musictrace = (cl_switch("-trace") || cl_switch("-t"));
miditrace = (cl_switch("-miditrace") || cl_switch("-m"));
do_midi_thru = !(cl_switch("-block"));
if (tuneFile != NULL) {
read_tuning(tuneFile);
user_retuning_on = true;
}
}
#endif
if (!initialized) {
initialized = true; /* must do early to avoid recursive loop! */
init_abort_handler();
setupMIDI(portA, 0x80);
if (num_channels > CHANNELS_PER_PORT) setupMIDI(portB, 0x80);
/* only initialize portB if necessary */
ticksAtStart = TickCount(); /* reset the clock */
}
if (first_time) { /* do this code only once */
first_time = false;
for (i = 0; i < MAX_CHANNELS; i++) {
bend[i] = -1; /* impossible value; fixed by synth_init */
midi_prog[i] = -1; /* impossible value; fixed by synth_init */
}
synth_init(); /* set initial synth state */
}
}
/****************************************************************************
* musicterm
* Effect:
* cleans up: turns off all voices, turns off interrupts
****************************************************************************/
#define ALL_NOTES_OFF 123
public void musicterm()
{
int i;
if (initialized) {
for (i = 1; i <= num_channels; i++) {
/* turns off all notes on all channels */
midi_ctrl(i, ALL_NOTES_OFF, 0);
}
/* turn off interrupts, abort watcher */
restoreMIDI(portA);
if (num_channels > CHANNELS_PER_PORT) restoreMIDI(portB);
cleanup_abort_handler();
initialized = false;
}
}
/****************************************************************************
* putbyte
* Inputs:
* int port: port to send on
* byte c: character to write
* Effect:
* writes c to the midi port if simulate is false
****************************************************************************/
private void putbyte(port, c)
int port;
byte c;
{
if (miditrace) gprintf(TRANS, "%3d ", c);
if (!simulate) Xmit(port, c);
}
/****************************************************************************
* random
* Inputs:
* int lo: lower limit of value
* int hi: upper limit of value
* Returns:
* int: random number (lo <= result <= hi)
****************************************************************************/
private ulong seed = 1534781;
public int random(lo, hi)
int lo, hi;
{
seed *= 13;
seed += 1874351;
return (int) ((long) lo +
((((long) hi + 1 - lo) * ((0x00FFFF00L & seed) >> 8)) >> 16));
}
/****************************************************************************
* read_tuning
* Inputs:
* char * filename: name of tuning file
* Effect:
* initializes re-tuning table with pitches and bends from .tun file
****************************************************************************/
public void read_tuning(filename)
char *filename;
{
int index, pit, lineno, i = 0;
float bend;
FILE *fp;
/* set up the default pitch table */
for (i = 0; i < 128; i++) {
pitch_tab[i].pbend = 8192;
pitch_tab[i].ppitch = i;
}
fp = fileopen(filename, "tun", "r", "Tuning definition file");
while ((fscanf(fp, "%d %d %f\n", &index, &pit, &bend) == 3) &&
(lineno < 128)) {
lineno++;
if (index >= -12 && index <= 115) {
pitch_tab[index+12].pbend = (int)(8192 * bend/100 + 8192);
pitch_tab[index+12].ppitch = pit;
}
}
}
/****************************************************************************
* settime
* Inputs:
* ulong absTime: time to set
* Effect:
* sets the clock to absTime
* Implementation:
* ticksAtStart is adjusted
****************************************************************************/
public void settime(absTime)
ulong absTime;
{
if (musictrace) gprintf(TRANS, "settime(%lu)\n", absTime);
ticksAtStart = TickCount() - HUNDREDTHS_TO_TICKS(absTime);
}
/****************************************************************************
* synth_init
* Effect:
* initialize all midi channels with reasonable start values
* NOTE:
* if "initialized" is not true at this point, you will get into
* a recursive loop when midi_bend and midi_program call fixup()
****************************************************************************/
void synth_init()
{
int i;
for (i = 1; i <= num_channels; i++) {
midi_bend(i, 8192); /* no bend */
midi_program(i, 1); /* default program = 1 */
midi_touch(i, 0);
midi_ctrl(i, PORTARATE, 99);
midi_ctrl(i, PORTASWITCH, 0); /* off */
midi_ctrl(i, MODWHEEL, 0); /* no modulation */
midi_ctrl(i, FOOT, 99);
abort_check(); /* give user a chance to abort from this loop */
}
}
/****************************************************************************
* timereset
* Effect:
* resets the clock
* Implementation:
* ticksAtStart is set to current value of system tick counter
****************************************************************************/
public void timereset()
{
if (musictrace) gprintf(TRANS, "timereset\n");
ticksAtStart = TickCount();
}
/****************************************************************************
* trace
* Inputs:
* boolean flag: true for trace on
* Effect:
* turns tracing on or off
****************************************************************************/
public void trace(flag)
boolean flag;
{
musictrace = flag;
}
/****************************************************************************
* tracemidi
* Inputs:
* boolean flag: true for trace on
* Effect:
* turns midi tracing on or off
****************************************************************************/
public void tracemidi(flag)
boolean flag;
{
miditrace = flag;
}